#ifndef ENTITY_HEADER
#define ENTITY_HEADER

/*
 *  Entity.h
 *  Oblivion++
 *
 *  Created by Billy Clack on 8/7/12.
 *  Copyright 2012 __MyCompanyName__. All rights reserved.
 *
 */


#include "glm/glm.hpp"
#include "glm/ext.hpp"
#include "Globals.h"
#include "Displayer.h"


class PhysicsPrimitive;
class GameManager;

struct GameEntityDef {
	PhysicsProperties pp;
};

struct PlayerGameEntityDef : public GameEntityDef {
	PlayerGameEntityDef() 
		: startPos(glm::vec2(0,0)),
			maxInputSpeed(1.0)
	{
	}

	glm::vec2 startPos;

	float maxInputSpeed;

	WeaponProperties startWeaponProps;
};

struct GameEntity {
	
	GameEntity(ENTITY_TYPE inType, GameManager * mgr);
	
	//Updates non-physics related stuff, such as input control,
	//game effects, shoot bullets if it's time, update graphics properties, etc.
	//Not every game entity will derive this, but if it's needed, go ahead...
	virtual void Update( float timeStep );

	PhysicsPrimitive * GetPhysicsPrimitive();
	
	ENTITY_TYPE mType; //The type of the object
	
	PhysicsPrimitive * mPhysPrim;


	GameManager * mManager;
	
};

//A hit record for shots.
struct HitRec {
	GameEntity * entity;
	glm::vec2 contactPoint;
};

class WeaponGameEntity : public GameEntity {
public:
	WeaponGameEntity( const WeaponProperties & inProps, GameManager * mgr );

	virtual void Fire( const glm::vec2 & origin, const glm::vec2 & target ) = 0; //All weapon classes must implement this

	virtual void Update( float timeStep );

protected:
	bool IsTimeToFire();

	void ResetFireCounter();

private:
	float mCurrFireCounter;

	WeaponProperties mProperties;
};

//This represents an instant weapon that means a raycast will occur
//in order to fire the weapon.
class RaycastWeaponGameEntity : public WeaponGameEntity {
public:
	RaycastWeaponGameEntity( const WeaponProperties & inProps, GameManager * mgr);

	virtual void Fire(const glm::vec2 & origin, const glm::vec2 & target);
};

struct GroundGameEntity : public GameEntity {
	GroundGameEntity( glm::vec2 * verts, int numVerts, GameManager * mgr);	
	
	void AddVerts( glm::vec2 * verts, int numVerts );
};

struct PlayerGameEntity : public GameEntity {
	PlayerGameEntity( const PlayerGameEntityDef & inDef,
		GameManager * mgr );
	
	//Gets only the velocity contributed from player input
	glm::vec2 GetInputVelocity();
	
	//Gets the total velocity, include player input + physical world velocity
	glm::vec2 GetTotalVelocity();
	
	void SetMaxInputSpeed( float speed );
	
	float GetMaxInputSpeed();
	
	void SetInputDirection( const glm::vec2  &dir );
	
	//Increment player's input velocity
	void IncrementInputSpeed( float speed);

	//Returns world position of player

	b2Vec2 GetPos();

	void SetState( PLAYER_STATE_T inState);
	PLAYER_STATE_T GetState();
	bool CheckStateBit( PLAYER_STATE_T inState );
	void SetStateBit( PLAYER_STATE_T inState);
	void RemoveStateBit( PLAYER_STATE_T inState);
	void SetTarget( const glm::vec2 & inTarget );
	void SetWeapon( WeaponGameEntity * inWeapon );
	virtual void Update( float timeStep);

private:
	
	void UpdateInputParams(float percentDecay);

	void DecayInputSpeed( float speed ) ;

	//Attacks based on current weapon statistics
	void Attack();
	
	int mCurrentState;
	
	glm::vec2 mInputDirection;
	float mInputSpeed;
	float mMaxInputSpeed;

	glm::vec2 mTarget; //What is the player aiming at right now?
	WeaponGameEntity * mCurrWeapon;

	double mTimeSinceLastAttack;
};


struct ItemGameEntityDef : public GameEntityDef {
	ItemGameEntityDef() 
		: spawnPos(glm::vec2(0,0)),
			type(ITEM_TYPE_HEALTH)
	{
	}

	glm::vec2 spawnPos; //Center pos
	ITEM_TYPE type;
	float value;
	float worldWidth;
	float worldHeight;

	SpriteDisplayPrimitiveDef dispDef;
};


//Helper functions
class ItemGameEntity : public GameEntity {
public:
	ItemGameEntity( const ItemGameEntityDef & inDef, GameManager * inMgr);

	void SetPos(const glm::vec2 & inPos) {
		mPos = inPos;
	}
	glm::vec2 GetPos(){
		return mPos;
	}
	void SetType(ITEM_TYPE inItem) {
		mType = inItem;
	}
	ITEM_TYPE GetType() {
		return mType;
	}
	float GetValue() {
		return mValue;
	}
	void SetValue(float inVal) {
		mValue = inVal;
	}

	SpriteDisplayPrimitive * GetDisplayPrimitive () {
		return mDisplayPrim;
	}

	void SetDisplayPrimitive(SpriteDisplayPrimitive * inPrim) {
		mDisplayPrim = inPrim;
	}

	//Inherited from GameEntity
	void Update( float ts );

private:
	glm::vec2 mPos;
	ITEM_TYPE mType;
	float mValue;
	float mWorldWidth;
	float mWorldHeight;

	SpriteDisplayPrimitive * mDisplayPrim;
};

struct ProjectileGameEntityDef : public GameEntityDef {
	ProjectileGameEntityDef () 
		: GameEntityDef(),
			worldPos(glm::vec2(0, 0)),
			startTarget(glm::vec2(1, 1)),
			speed(0.1),
			wh(glm::vec2(0.25, 0.25)),
			mDoGravity(false)
	{
	}

	glm::vec2 worldPos;
	glm::vec2 wh;
	glm::vec2 startTarget;
	float speed; //The speed of the projectile. m/s
	bool mDoGravity; //Is it affected by gravity? No for missiles and such.

	SpriteDisplayPrimitiveDef dispDef;
};

class ProjectileGameEntity : public GameEntity {
public:
	ProjectileGameEntity( const ProjectileGameEntityDef & inDef, GameManager * inMgr) 
		: GameEntity(ENTITY_PROJECTILE, inMgr) ,
		mDispPrim(NULL)
	{
		mDispPrim = new SpriteDisplayPrimitive(&(inDef.dispDef));

		SetPos(inDef.worldPos);
		SetSpeed(inDef.speed);
 
		PhysicsProperties pp = inDef.pp;

		mPhysPrim = PhysicsFactory::CreateBox(
			b2Vec2(mWorldPos.x + inDef.wh.x/2, mWorldPos.y + inDef.wh.y/2),
			b2Vec2(mWorldPos.x - inDef.wh.x/2, mWorldPos.y + inDef.wh.y/2),
			inDef.wh.y, 
			inDef.pp);

	}

	void SetPos(const glm::vec2 & inPos) { 
		mWorldPos = inPos;
		mDispPrim->SetPos(mWorldPos);
	}
	
	glm::vec2 GetPos() { return mWorldPos; }

	void SetSpeed(float inSpeed ) { speed = inSpeed; }

	float GetSpeed() { return speed; }

	void Update(float ts) {
		glm::vec2 pos = glm::vec2(mPhysPrim->GetPos().x, mPhysPrim->GetPos().y);
		SetPos(pos);
	}

	SpriteDisplayPrimitive * GetDisplayPrimitive() { return mDispPrim; }

private:

	SpriteDisplayPrimitive * mDispPrim;

	glm::vec2 mWorldPos;
	float speed;
};

WeaponGameEntity * CreateWeapon( const WeaponProperties & inProps,
	GameManager * manager);

#endif